package com.plumecache.core.interceptor;

import com.plumecache.core.CacheService;
import com.plumecache.core.annotation.IgnoreInterceptor;
import com.plumecache.core.exception.PlumeCacheException;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author yinyansheng
 */
@Slf4j
public class CacheInterceptorProxyInvocationHandler implements InvocationHandler {

    private CacheService target;

    private static final CacheInterceptorChain CACHE_INTERCEPTOR_CHAIN;

    static {
        CACHE_INTERCEPTOR_CHAIN = initializeCacheInterceptorChain();
    }

    public CacheInterceptorProxyInvocationHandler(CacheService target) {
        this.target = target;
    }

    @SneakyThrows
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        if (!(proxy instanceof CacheService)) {
            throw new PlumeCacheException("not support target:" + proxy.getClass());
        }

        if (method.getAnnotation(IgnoreInterceptor.class) != null) {
            return method.invoke(target, args);
        }

        HashMap<String, Object> context = new HashMap<>();

        try {
            CACHE_INTERCEPTOR_CHAIN.applyPreHandle(target, method, args, context);
            Object result = method.invoke(target, args);
            CACHE_INTERCEPTOR_CHAIN.applyPostHandle(target, method, args, context, result);
            return result;
        } catch (Exception ex) {
            CACHE_INTERCEPTOR_CHAIN.triggerAfterCompletion(target, method, args, context, ex);
            throw new PlumeCacheException(ex);
        }
    }

    public static CacheInterceptorChain initializeCacheInterceptorChain() {
        ServiceLoader<CacheInterceptor> serviceLoader = ServiceLoader.load(CacheInterceptor.class);
        CacheInterceptorChain cacheInterceptorChain = new CacheInterceptorChain();
        List<CacheInterceptor> cacheInterceptors = new ArrayList<>();
        serviceLoader.forEach(cacheInterceptors::add);
        cacheInterceptorChain.setInterceptors(cacheInterceptors);
        return cacheInterceptorChain;
    }

    public static void filterInterceptor(Set<String> include, Set<String> exclude) {
        if (null != CACHE_INTERCEPTOR_CHAIN && null != CACHE_INTERCEPTOR_CHAIN.getInterceptors()) {
            List<CacheInterceptor> interceptors = CACHE_INTERCEPTOR_CHAIN.getInterceptors();

            List<CacheInterceptor> filter = new ArrayList<>();
            filter.addAll(interceptors.stream().filter(r -> r.isEnable() && !exclude.contains(r.getClass().getSimpleName())).collect(Collectors.toList()));
            filter.addAll(interceptors.stream().filter(r -> !r.isEnable() && include.contains(r.getClass().getSimpleName())).collect(Collectors.toList()));

            CACHE_INTERCEPTOR_CHAIN.setInterceptors(filter);
        }
    }
}
